import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.LayoutManager;
import java.awt.Paint;
import java.awt.Toolkit;
import java.awt.color.ColorSpace;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
import java.util.LinkedList;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;


public class Display extends JFrame implements Runnable{
	
	private Game game;
	private boolean painting;
	private LinkedList<Drawable>drawableObjects;
	private final int MIN_DRAWABLE_SIZE = 2;
	private BufferStrategy strategy;
	private final int barSize = 100;
	private Point center;
	private boolean canPaint;
	private boolean typing;
	private TypingPanel typingPanel;
	private JFrame typingFrame;
	private BufferedImage background;
	private BufferedImage darkground;
	private Point characterCenter;
	/**
	 * initializes the display object's instance variables.  The first few lines also put the
	 * game into fullscreen view.
	 * @param game
	 */
	public Display(Game game)
	{
	//	DisplayMode dm = new DisplayMode(800,600,16,DisplayMode.REFRESH_RATE_UNKNOWN);
		Screen s = new Screen();
	//	s.setFullScreen(null, this);
	//	s.restoreScreen();
		this.game = game;
		this.setVisible(true);
		painting = false;
		KeyListener key = new KeyPresses(this);
		this.addKeyListener(key);
		this.resize(300,300);
		this.createBufferStrategy(2);
		strategy = this.getBufferStrategy();
		drawableObjects = new LinkedList<Drawable>();
		this.setExtendedState(MAXIMIZED_BOTH);
		this.center = new Point(0,0);
		canPaint = true;
		this.background = GameSettings.getImageStorage().getMap(0);
		darkground = new BufferedImage(background.getWidth(),background.getHeight(),BufferedImage.TYPE_INT_ARGB);
		darkground.getGraphics().drawImage(background,0,0,null);
		ColorConvertOp op = 
		    new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
		op.filter(darkground, darkground);
		/*make the program end when someone presses the exit button of
		the JFrame.  Also attempts to make typing panel behave similiarly to the
		normal display.  However, sometimes it still doesn't minimize at the right times...don't know why though.
		*/
		this.addWindowListener( new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent we) {
            	try{
            	GameSettings.getDBComm().close();
            	}
            	catch(Exception e)
            	{
            		
            	}
                System.exit(0);
            }
            public void windowIconified(WindowEvent e) {
                typingFrame.setState(JFrame.ICONIFIED);
            }
            public void windowOpened(WindowEvent e) {
            	if(typingFrame != null)
            	{
            		typingFrame.setState(JFrame.NORMAL);
            	}
            }
            public void windowDeiconified(WindowEvent e) {
            	if(typingFrame != null)
            	{
            		typingFrame.setState(JFrame.NORMAL);
            	}
            }
            public void windowGainedFocus(WindowEvent e) {
            	typingFrame.setState(JFrame.NORMAL);
            	typingFrame.setAlwaysOnTop(true);
            }
            public void windowLostFocus(WindowEvent e) {
            	typingFrame.setAlwaysOnTop(false);
            }
        } );
		addTypingPanel();
	}
	/**
	 * adds the gui for the chat server to the lower right hand corner of the screen
	 */
	public void addTypingPanel()
	{
		typingFrame = new JFrame();
		typingPanel = new TypingPanel(this,game);
		typingFrame.getContentPane().add(typingPanel);
		typingFrame.setVisible(true);
		typingFrame.resize(100,500);
		typingFrame.setResizable(false);
		typingFrame.removeNotify();
		typingFrame.setUndecorated(true);
		int x = (int)this.getLocation().getX() + this.getWidth();
		int y = (int)this.getLocation().getY() + this.getHeight();
		typingFrame.setLocation(x,y);
		typingFrame.addNotify();
		typingFrame.setAlwaysOnTop(true);
	}
	/**
	 * displays the string message on the screen (or at least it's supposed to. 
	 * I still need to fix this method. It's on my todo list I swear
	 */
	public void displayMsg(String str)
	{
		JPanel panel = new JPanel(new GridLayout(3, 1));
		Font font = new Font("sansserif", Font.BOLD, 32);
		JLabel label = new JLabel(str);
		label.setFont(font);
		label.setForeground(Color.RED);
		panel.setSize(200,200);
		panel.add(label);
		panel.setBackground(Color.GRAY);
		this.getContentPane().add(panel);
		this.setVisible(true);
		repaint();
	}
	/**
	 * same thing as displayMsg except that this one causes the
	 * program to close after a set amount of time.
	 * @param str the message to be displayed before the program closes
	 */
	public void displayFatalMsg(String str)
	{
		JPanel panel = new JPanel(new GridLayout(3, 1));
		Font font = new Font("sansserif", Font.BOLD, 10);
		JLabel label = new JLabel(str);
		label.setFont(font);
		label.setForeground(Color.RED);
		panel.add(label);
		panel.setBackground(Color.GRAY);
		this.getContentPane().add(panel);
		this.setVisible(true);
		repaint();
		try {
			Thread.sleep(7000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.exit(0);
		//this.setVisible(false);
		//this.dispose();
	}
	/**
	 * this is what I USED to use to display the screen, it
	 * was the source of all of the flickering you used to see, so I killed it
	 */
	public void paint(Graphics g)
	{

	}
	/**
	 * This class implements KeyListener, and handles all of the
	 * key presses and releases in the game.  This is how we
	 * tell the server when someone has presses left or right or space, etc.
	 */
	private class KeyPresses implements KeyListener
	{

		JFrame frame;
		public KeyPresses(JFrame frame)
		{
			this.frame = frame;
		}
		/**
		 * handles all key presses
		 */
		@Override
		public void keyPressed(KeyEvent e) {
			int var = e.getKeyCode();
				if(var == GameSettings.UP)
				{
					if(typing == false)
					{
						game.pressedUp();
					}
				}
				else if(var == GameSettings.DOWN)
				{
					if(typing == false)
					{
						game.pressedDown();
					}
				}
				if(var == GameSettings.LEFT)
				{
					if(typing == false)
					{
						game.pressedLeft();
					}
				}
				else if(var == GameSettings.RIGHT)
				{
					if(typing == false)
					{
						game.pressedRight();
					}
				}
				if(var == GameSettings.ENTER)
				{
					typingPanel.typeMessage();
				}
				else if(var == GameSettings.SPACE)
				{
					game.pressedSpace();
				}
				else if(var == GameSettings.B)
				{
					game.pressedB();
				}
			}
		/**
		 * handles all key releases
		 */
		@Override
		public void keyReleased(KeyEvent e) {
			int var = e.getKeyCode();
			if(var == GameSettings.UP)
			{
				if(typing == false)
				{
					game.releasedUp();
				}
			}
			else if(var == GameSettings.DOWN)
			{
				if(typing == false)
				{
					game.releasedDown();
				}
			}
			else if(var == GameSettings.LEFT)
			{
				if(typing == false)
				{
					game.releasedLeft();
				}
			}
			else if(var == GameSettings.RIGHT)
			{
				if(typing == false)
				{
					game.releasedRight();
				}
			}
			else if(var == GameSettings.SPACE)
			{
				if(typing == false)
				{
					game.releasedSpace();
				}
			}
			else if(var == GameSettings.B)
			{
				if(typing == false)
				{
					game.releasedB();
				}
			}
		}

		@Override
		public void keyTyped(KeyEvent e) {
			// TODO Auto-generated method stub
			
		}
		
	}
	/**
	 * resizes the typing frame based on the size of the
	 * JFrame(the main window)
	 */
	public void resizeTypingFrame()
	{
		int sizex = this.getWidth()/8;//Math.min(70,this.getWidth()/8);
		int sizey = this.getHeight()/3;//Math.min(150, this.getHeight()/3);
		//sizex = Math.max(30, this.getWidth()/8);
		//sizey = Math.max(70, this.getHeight()/3);
		typingFrame.setSize(sizex,sizey);
		int x = (int)this.getLocation().getX() + this.getWidth() - sizex - 5;
		int y = (int)this.getLocation().getY() + this.getHeight() - sizey - 5;
		typingFrame.setLocation(x,y);
		typingFrame.repaint();
	}
	/**
	 * this is the main method of display.  It loops as long as the
	 * game is open, and every time it loops it updates the display.
	 */
	@Override
	public void run() {

		int t = 0;
		while( t == 0)
		{
			if(canPaint == true)
			{
				canPaint = false;
				Graphics2D temp = (Graphics2D)strategy.getDrawGraphics();
				temp.setColor(Color.BLACK);
				Point p = new Point(0,0);
				characterCenter = game.getCenter();
				p = this.center(p,characterCenter,center);
				try{
				if(game.getDark()){
				drawDarkground(temp,p);
				}
				else
				{
				drawBackground(temp,p);
				}
				}
				catch(Exception e)
				{
					System.out.println("Error in drawing background");
				}
				drawObjects(temp);
				drawAttacks(temp);
				drawFrame(temp);
				temp.dispose();
				strategy.show();
				resizeTypingFrame();
			}
			try {
				Thread.sleep(GameSettings.getScreenUpdateRate());
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
	/**
	 * draws the background onto the new Graphics object
	 * @param temp the graphics object
	 * @param p the position of the player
	 */
	public void drawBackground(Graphics2D temp,Point p)
	{
		int x = (int)(characterCenter.getX() - (this.getWidth()/2));
		int y = (int)(characterCenter.getY() - (this.getHeight()/2));
		BufferedImage img = background.getSubimage(x,y,this.getWidth(), this.getHeight());
		temp.drawImage(img,0,0,null);
	}
	/**
	 * draws the background onto the new Graphics object
	 * @param temp the graphics object
	 * @param p the position of the player
	 */
	public void drawDarkground(Graphics2D temp,Point p)
	{
		int x = (int)(characterCenter.getX() - (this.getWidth()/2));
		int y = (int)(characterCenter.getY() - (this.getHeight()/2));
		BufferedImage img = darkground.getSubimage(x,y,this.getWidth(), this.getHeight());
		temp.drawImage(img,0,0,null);
	}
	/**
	 * sets the background of the game
	 * @param background the image to be used as a background
	 * WARNING: Image used for background must be big enough so that something
	 * will always be displayed on the screen, or else you will get an error
	 */
	public void setBackground(BufferedImage background)
	{
		this.background = background;
	}
	/**
	 * gets a chat message and sends it to the typing panel
	 * to be displayed
	 * @param str the chat message
	 */
	public void receiveChat(String str)
	{
		typingPanel.receiveChat(str);
	}
	/**
	 * tells the display that everything has been loaded and prepared in
	 * the game and it can now start displaying the screen
	 */
	public void canPaint()
	{
		canPaint = true;
	}
	/**
	 * draws all the wave type attacks
	 * @param temp
	 */
	public void drawAttacks(Graphics2D temp)
	{
		LinkedList<Drawable> items = game.getAttacks();
		
		if(items!= null)
		{
			for(int p = 0;p<items.size();p++)
			{
				Drawable i = items.get(p);
				// Rotation information
				if(i!=null)
				{
					float rotationRequired = i.getAngle();
					// Drawing the rotated image at the required drawing locations
					this.center = new Point(this.getWidth()/2,this.getHeight()/2);
					int length = i.getExtra();
					if(length == 1994)  //a blast, not a wave
					{
						double locationX = i.getCenter().getX();
						double locationY = i.getCenter().getY();
						AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
						AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
						// Drawing the rotated image at the required drawing locations
						this.center = new Point(this.getWidth()/2,this.getHeight()/2);
						Point x = centerAttack(i.getPoint(),characterCenter,this.center,rotationRequired);
						if(i.getAngle() < Math.toRadians(90))
						{
							temp.drawImage(op.filter(i.getImage(), null), (int)(x.getX() + Math.cos(rotationRequired)*(i.getWidth()/2) - (i.getWidth()/2) + 32), (int)(x.getY() + Math.sin(rotationRequired)*(i.getWidth()/2) - (i.getWidth()/2)- 5), null);
						}
						else
						{
							AffineTransform tx2 = AffineTransform.getScaleInstance(1, -1);
							tx2.translate(0, -i.getImage().getHeight());
							AffineTransformOp op2 = new AffineTransformOp(tx2, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
							BufferedImage image = op2.filter(i.getImage(), null);
							temp.drawImage(op.filter(i.getImage(), null), (int)(x.getX() + Math.cos(rotationRequired)*(i.getWidth()/2) - (i.getWidth()/2) + 32), (int)(x.getY() + Math.sin(rotationRequired)*(i.getWidth()/2) - (i.getWidth()/2)- 5), null);
						}
					}
					else
					{
						if(length >0)
						{
							Image a = (i.getImage().getScaledInstance(length, 20, Image.SCALE_DEFAULT));
							BufferedImage buffered = new BufferedImage((int)(length) + 20,(length) + 20, i.getImage().getType());
							buffered.getGraphics().drawImage(a, 10, buffered.getHeight()/2 - a.getHeight(null)/2 , null);
							AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired,(length/2) + 10,buffered.getHeight()/2);
							AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
							BufferedImage buffered2 = new BufferedImage(length,length + 20,BufferedImage.TYPE_INT_ARGB);
							buffered2.getGraphics().drawImage(op.filter(buffered, null),0,0,null);
							ImageFilter filter = new RGBImageFilter() 
					        {
		
					              public int markerRGB = Color.BLACK.getRGB() | 0xFF000000;               //color to make transparent
		
					              public final int filterRGB(int x, int y, int rgb) 
					              {
					                if ( ( rgb | 0xFF000000 ) == markerRGB ) 
					                {
					                  // Mark the alpha bits as zero - transparent
					                  return 0x00FFFFFF & rgb;
					                }
		
					                else 
					                {
					                  // nothing to do
					                  return rgb;
					                }
					             }
					        }; 
		
							ImageProducer ip = new FilteredImageSource(op.filter(buffered,null).getSource(), filter);
							Image img = Toolkit.getDefaultToolkit().createImage(ip);
							Point x = centerAttack(i.getPoint(),characterCenter,this.center,rotationRequired);//centerAttack(i.getPoint(),characterCenter,this.center,rotationRequired,length);
							temp.drawImage(img, (int)(x.getX()+Math.cos(rotationRequired)*(length/2) - (length/2)) + 32, (int)(x.getY() + Math.sin(rotationRequired)*(length/2) - (length/2)) -17, null);
							System.out.println(Math.cos(rotationRequired));
						}
					}
				}
				
			}
		}
	}
	/**
	 * draws all of the health meters for each character
	 * @param temp the graphics object to draw the meters on
	 */
	public void drawFrame(Graphics2D temp){
		LinkedList<Drawable> d = game.getDrawableStats();
		if(d != null)
		{
			for(Drawable i:d)
			{
				drawMeter(temp,i,7);
			}
		}
	}
	/**
	 * draws the health meter for the drawable oject sent in the upper left hand corner
	 * @param temp the graphics object to draw the health meters on
	 * @param img the image that contains the data for the health meter
	 * @param xpos how far over the health meter should be drawn
	 */
	private void drawMeter(Graphics2D temp,Drawable img, int xpos){
		int faceLength = 60;
		int barHeight = 25;
		temp.drawImage(img.getImage(), xpos, 15, faceLength,faceLength,null);
		temp.setColor(Color.GREEN);
		temp.drawRect(xpos + faceLength, 15, barSize, barHeight);
		temp.fillRect(xpos+ faceLength, 15, (int)img.getPoint().getX()*barSize,barHeight);
		temp.setColor(Color.BLUE);
		temp.drawRect(xpos + faceLength, 15 + barHeight, barSize, barHeight);
		temp.fillRect(xpos+ faceLength, 15 + barHeight, (int)img.getPoint().getY()*barSize,barHeight);
	}
	/**
	 * draws the objects of the game, like obstacles and players
	 * Draws everything relative to the player's location
	 */
	private void drawObjects(Graphics2D temp)
	{
		painting = true;
		Point characterCenter = game.getCenter();
		LinkedList<Drawable>items = game.getDrawableList();
		if(items != null)
		{
			if(items.size()>=MIN_DRAWABLE_SIZE)
			{
				drawableObjects = items;
			}
			else
			{
				items = drawableObjects;
			}
		}
		else
		{
			items = drawableObjects;
		}
		if(drawableObjects != null)
		{
			for(int p = 0;p<drawableObjects.size();p++)
			{
				Drawable i = items.get(p);
				// Rotation information
				if(i!=null)
				{
					double rotationRequired = i.getAngle();
					double locationX = i.getCenter().getX();
					double locationY = i.getCenter().getY();
					AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
					AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
					// Drawing the rotated image at the required drawing locations
					this.center = new Point(this.getWidth()/2,this.getHeight()/2);
					Point x = center(i.getPoint(),characterCenter,this.center);
					
					if(i.getAngle() < Math.toRadians(90))
					{
						temp.drawImage(op.filter(i.getImage(), null), (int)x.getX(), (int)x.getY(), null);
					}
					else
					{
						AffineTransform tx2 = AffineTransform.getScaleInstance(1, -1);
						tx2.translate(0, -i.getImage().getHeight());
						AffineTransformOp op2 = new AffineTransformOp(tx2, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
						BufferedImage image = op2.filter(i.getImage(), null);
						temp.drawImage(op.filter(image, null), (int)x.getX(), (int)x.getY(), null);
					}
				}
				else
				{
				}
			}
		}
	}
	/**
	 * adjusts the given point on the screen to the center with the player as the center 
	 * @param img the image position to be drawn
	 * @param player the players position, which will be used as the center
	 * @param frame the center of the screen
	 * @return the position where the img should be drawn
	 */
	public Point center(Point img,Point player,Point frame)
	{
		Point diff = new Point(0,0);
		diff.setX(frame.getX()-player.getX());
		diff.setY(frame.getY()-player.getY());
		return new Point(img.getX() + diff.getX(),img.getY()+diff.getY());
		
	}
	/**
	 * adjusts the given point on the screen to the center with the player as the center 
	 * @param img the image position to be drawn
	 * @param player the players position, which will be used as the center
	 * @param frame the center of the screen
	 * @return the position where the img should be drawn
	 */
	public Point centerAttack(Point img,Point player,Point frame,float rotationRequired)
	{
		Point diff = new Point(0,0);
		diff.setX(frame.getX()-player.getX() + (int)(30*Math.cos(rotationRequired)));
		diff.setY(frame.getY()-player.getY() + (int)(30*Math.sin(rotationRequired)));
		Point ans = new Point(img.getX() + diff.getX(),img.getY()+diff.getY() + 50);
		return ans;
		
	}
	
}
